{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7f82bc10-e64b-49c9-ab05-6e9763ae200c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# File: yarn.ipynb\n",
    "# Code: Claude Code and Codex\n",
    "# Review: Ryoichi Ando (ryoichi.ando@zozo.com)\n",
    "# License: Apache v2.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b0470560-66f1-4db7-a706-7a04fa5a4865",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from frontend import App\n",
    "\n",
    "\n",
    "# function to generate interwoven yarn strands with sinusoidal pattern\n",
    "def make_strands(offset: float, width: float, res: float):\n",
    "\n",
    "    offset = 1.3 * offset\n",
    "    n_vertical_yarns = int(res * 0.28 / offset)\n",
    "    n_points_per_seg = int(res * 20)\n",
    "    n_segs = int(width * n_vertical_yarns)\n",
    "    n_points = n_segs * n_points_per_seg\n",
    "    dx = 1.0 / (n_points - 1)\n",
    "    strands = []\n",
    "\n",
    "    # create vertical yarn strands with sinusoidal weaving pattern\n",
    "    for k in range(n_vertical_yarns):\n",
    "        y_base = k / (n_vertical_yarns - 1) - 0.5 if n_vertical_yarns > 1 else 0.0\n",
    "        z_base = 0.0\n",
    "        j_vals = np.arange(n_points)\n",
    "        x_mod = (j_vals % n_points_per_seg) / n_points_per_seg\n",
    "        t = 2.0 * np.pi * x_mod\n",
    "        x_disp = -width * (0.5 / n_segs) * np.sin(2.0 * t)\n",
    "        y_disp = (0.85 / n_vertical_yarns) * np.sin(t)\n",
    "        z_disp = 0.75 * offset * np.cos(2.0 * t)\n",
    "        x_coord = width * (2.0 * dx * j_vals - 1.0) + x_disp\n",
    "        y_coord = y_base + y_disp\n",
    "        z_coord = z_base + z_disp\n",
    "        xyz = np.zeros((n_points, 3))\n",
    "        xyz[:, 0] = x_coord\n",
    "        xyz[:, 1] = y_coord\n",
    "        xyz[:, 2] = z_coord\n",
    "        strands.append((xyz, False))\n",
    "\n",
    "    # create horizontal circular yarn loops at edges\n",
    "    for pos_index in range(2):\n",
    "        for k in range(n_segs - 1):\n",
    "            dx_local = 2.0 * width / n_segs\n",
    "            y_base = 0.5 + 0.25 * dx_local if pos_index == 0 else -0.5 - 0.25 * dx_local\n",
    "            z_base = 0.15 * dx_local\n",
    "            x_center = dx_local * (k + 0.77) - width\n",
    "            if pos_index == 1:\n",
    "                x_center += 0.5 * dx_local\n",
    "            j_vals = np.arange(n_points_per_seg)\n",
    "            t = 2.0 * np.pi * j_vals / n_points_per_seg\n",
    "            r = 0.78 * width / n_segs\n",
    "            z_val = r * np.cos(t)\n",
    "            theta = 0.25 * np.pi\n",
    "            x_coord = x_center + r * np.sin(t)\n",
    "            if pos_index == 0:\n",
    "                y_coord = y_base + z_val * np.sin(theta)\n",
    "            else:\n",
    "                y_coord = y_base - z_val * np.sin(theta)\n",
    "            z_coord = z_base + z_val * np.cos(theta)\n",
    "            xyz = np.zeros((n_points_per_seg, 3))\n",
    "            xyz[:, 0] = x_coord\n",
    "            xyz[:, 1] = y_coord\n",
    "            xyz[:, 2] = z_coord\n",
    "            strands.append((xyz, True))\n",
    "\n",
    "    return strands\n",
    "\n",
    "\n",
    "# create an app\n",
    "app = App.create(\"yarn\")\n",
    "\n",
    "# create a scene\n",
    "scene = app.scene.create()\n",
    "\n",
    "# generate and add all yarn strands\n",
    "for k, (V, closed) in enumerate(make_strands(4e-3, 0.5, 1.0)):\n",
    "    # create edge connectivity for rod strands\n",
    "    E = [[i, i + 1] for i in range(len(V) - 1)]\n",
    "    if closed:\n",
    "        E.append([len(V) - 1, 0])\n",
    "    name = f\"strand-{k}\"\n",
    "    app.asset.add.rod(name, V, np.array(E, dtype=np.uint32))\n",
    "    obj = scene.add(name)\n",
    "    # set material properties for yarn strands\n",
    "    (\n",
    "        obj.param.set(\"bend\", 0.0)\n",
    "        .set(\"young-mod\", 1e5)\n",
    "        .set(\"contact-gap\", 1e-3)\n",
    "        .set(\"contact-offset\", 2.3e-3)\n",
    "        .set(\"length-factor\", 0.85)\n",
    "    )\n",
    "    # pull open-ended strands from both ends\n",
    "    if not closed:\n",
    "        move_delta, t_end = -5, 10\n",
    "        obj.pin(obj.grab([-1, 0, 0])).move_by([move_delta, 0, 0], 0, t_end)\n",
    "        obj.pin(obj.grab([1, 0, 0])).move_by([-move_delta, 0, 0], 0, t_end)\n",
    "\n",
    "# compile the scene and report stats\n",
    "scene = scene.build().report()\n",
    "\n",
    "# preview the initial scene\n",
    "scene.preview()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f15496ae-0ebc-495f-b0f7-4625915db4e4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# create a new session with the compiled scene\n",
    "session = app.session.create(scene)\n",
    "\n",
    "# set session parameters - disable gravity\n",
    "session.param.set(\"frames\", 120).set(\"dt\", 1e-2).set(\"gravity\", 0.0)\n",
    "\n",
    "# build this session\n",
    "session = session.build()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5854e6b9-a1db-457d-bb06-67775834fecf",
   "metadata": {},
   "outputs": [],
   "source": [
    "# start the simulation and live-preview the results\n",
    "session.start().preview()\n",
    "\n",
    "# also show simulation logs in realtime\n",
    "session.stream()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "934c72fb-1d70-4265-897f-39e956c1fbee",
   "metadata": {},
   "outputs": [],
   "source": [
    "# create an animation from the simulation results\n",
    "session.animate()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4b4f719f-49a5-4dea-80dd-a5c4c12e35c5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# export the animation to file\n",
    "session.export.animation()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "749a460a-23af-4304-9fa1-66fb17245077",
   "metadata": {},
   "outputs": [],
   "source": [
    "# this is for CI\n",
    "if app.ci:\n",
    "    assert session.finished()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
